﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Effects.Effects;

namespace Effects.GUI
{
    /// <summary>
    ///  Form to edit settings' change data.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   It is very simple form that is used by <see cref="ChangesManagerVisualizer"/> class for adding and editing settings' changes/
    ///  </para>
    ///  <para>
    ///   It has few modes that it works in. 
    ///  </para>
    ///  <para>
    ///   Normal mode - edit is false, allParams is false. User is adding new change. So he can edit all controls.
    ///  </para>
    ///  <para>
    ///   Edit mode - edit is tru, allParams false. User is editing previously added valu. He can edit only value. Time and paramter stays the same.
    ///  </para>
    ///  <para>
    ///   Initial Settings mode - allParams is true. User is editing initial settings. Therefore editing time is pointless, but all other parameters must be set.
    ///  </para>
    /// </remarks>
    public partial class AddParamChangeForm : Form
    {
        /// <summary>
        ///  Editing mode on/off.
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        private bool edit;

        /// <summary>
        ///  Turn on or off the editing mode.
        /// </summary>
        public bool Edit
        {
            get { return edit; }
            set
            {
                edit = value;
                // allParams don't set anything
                if (!allParams)
                {
                    this.ParamterChooseBox.Enabled = !edit;
                    //this.MinTextBox.Enabled = !edit;
                    EnableTimeTextBoxe(!edit);
                }
            }
        }

        /// <summary>
        ///  All parameters (Initial settings mode) on/off.
        /// </summary>
        private bool allParams;

        /// <summary>
        ///  Turns on/off Initial settings mode.
        /// </summary>
        public bool AllParams
        {
            get { return allParams; }
            set
            {
                allParams = value;
                if (allParams)
                {
                    //MinTextBox.Enabled = !allParams;
                    EnableTimeTextBoxe(!allParams);
                    ParamterChooseBox.Enabled = true;
                }
                else
                    Edit = edit;
            }
        }

        /// <summary>
        ///  Currently choosen (and being edited) parameter
        /// </summary>
        /// <remarks>
        ///  Nullable type is necessary, because at first no paramter is choosen.
        /// </remarks>
        private EffectParam ? currentParam;

        /// <summary>
        ///  Key of the parameter user has set.
        /// </summary>
        private object currentKey;

        /// <summary>
        ///  Gets the key of the parameter user has set.
        /// </summary>
        /// <remarks>
        ///  Valid only when <see cref="AddParamChangeForm.OK"/> is true
        /// </remarks>
        public object Key
        {
            get { return currentKey; }
        }

        /// <summary>
        ///  Value of the parameter user has set.
        /// </summary>
        private object currentValue;

        /// <summary>
        ///  Gets the value of the parameter user has set.
        /// </summary>
        /// <remarks>
        ///  Valid only when <see cref="AddParamChangeForm.OK"/> is true
        /// </remarks>
        public  object Value
        {
            get {return currentValue;}
        }

        /// <summary>
        ///  Time of the cahnge user has set
        /// </summary>
        private DateTime currentTime;

        /// <summary>
        ///  Gets the time of change user has set.
        /// </summary>
        /// <remarks>
        ///  Valid only when <see cref="AddParamChangeForm.OK"/> is true
        /// </remarks>
        public DateTime Time
        {
            get { return currentTime; }
        }

        /// <summary>
        ///  Creates new instance of <see cref="AddParamChangeForm"/>
        /// </summary>
        public AddParamChangeForm()
        {
            InitializeComponent();
            this.currentParam = null;
            this.LowerBoundLabel.Hide();
            this.UpperBoundLabel.Hide();
            this.ValueBox.Hide();
            this.ListTypeComboBox.Hide();
            this.Text = "Edit parameter change";
        }

        /// <summary>
        ///  Parameters to choose from.
        /// </summary>
        private EffectParam[] parameters;

        /// <summary>
        ///  Gets or sets the parameters that user can choose from
        /// </summary>
        public EffectParam[] Parameters
        {
            get { return parameters; }
            set 
            { 
                this.parameters = value;
                this.values = new object[parameters.Length];
                Prepare();
            }
        }

        /// <summary>
        ///  Finds index of given <see cref="global::Effects.Effects.EffectParam"/> in <see cref="AddParamChangeForm.parameters"/>
        /// </summary>
        /// <param name="param"> Param to find </param>
        /// <returns> Index of given param in <see cref="AddParamChangeForm.parameters"/>, -1 if it is no contained </returns>
        private int FindParam(EffectParam param)
        {
            int index = -1;
            for (int i = 0; i < parameters.Length; i++)
                if (parameters[i].Equals(param))
                    index = i;

            return index;
        }

        /// <summary>
        ///  Array containing values set by user.
        /// </summary>
        /// <remarks>
        ///  Value here is described in <see cref="global::Effects.Effects.EffectParam"/>
        ///  This array has same length as <see cref="AddParamChangeForm.parameters"/> and therefore values[index] is value for paramters[index]
        /// </remarks>
        private object[] values;

        /// <summary>
        ///  Gets or sets the values of the parameters. Null value is ignored and not set.
        /// </summary>
        public object[] Values
        {
            get { return values; }
            set { if(value != null)
                    values = value; }
        }

        /// <summary>
        ///  Sets form to show given values.
        /// </summary>
        /// <param name="key"> Parameter's key </param>
        /// <param name="value"> Value to show  </param>
        /// <param name="time"> time to show  </param>
        public void SetParam(object key,object value,DateTime time)
        {
            EffectParam param = Find(key);
            this.ParamterChooseBox.SelectedItem = param;

            this.currentTime = time;
            SetTime(time);
            this.currentKey = key;
            //this.ParamterChooseBox.SelectedValue = param.Name;

            switch (param.ValueType)
            {
                case ParameterType.Integer:
                    this.ValueBox.Text = value.ToString();
                    break;
                case ParameterType.Real:
                    this.ValueBox.Text = value.ToString();
                    break;
                case ParameterType.List:
                    ListItem[] pairs = (ListItem[])param.Bounds;
                    for (int i = 0; i < pairs.Length; i++)
                        if(value.Equals(pairs[i].Value))
                            ListTypeComboBox.SelectedItem = pairs[i].DisplayName;
                    break;
            }

        }

        /// <summary>
        ///  Finds parameter with given key.
        /// </summary>
        /// <param name="key"> Key of the parameter </param>
        /// <returns> <see cref="EffectParam"/>  from <see cref="AddParamChangeForm.parameters"/> with given key</returns>
        private EffectParam Find(object key)
        {
            for (int i = 0; i < parameters.Length; i++)
                if (parameters[i].Key.Equals(key))
                    return parameters[i];
            
            return new EffectParam();
        }

        /// <summary>
        ///  Prepares form. Loads <see cref="AddParamChangeForm.ParamterChooseBox"/> with itmes correspoding to parameters
        /// </summary>
        private void Prepare()
        {
            this.ParamterChooseBox.Items.Clear();
            for (int i = 0; i < parameters.Length; i++)
            {
                this.ParamterChooseBox.Items.Add(parameters[i]);
            }
        }

        /// <summary>
        ///   <see cref="AddParamChangeForm.ParamterChooseBox"/> selected value changed event handler.
        ///   Reacts to parameter change save current value and prepare form for new one.
        /// </summary>
        /// <param name="sender"> Object that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        private void ParamterChooseBox_SelectedValueChanged(object sender, EventArgs e)
        {
            try
            {
                // if param stays the same, there's nothing to do
                if (currentParam != null && ((EffectParam) currentParam).Equals((EffectParam)ParamterChooseBox.SelectedItem))
                    return;

                int index;
                if (currentParam != null )
                {
                    index = FindParam((EffectParam)currentParam);
                    //index = ParamterChooseBox.SelectedIndex;

                    // save current value
                    values[index] = GetCurrentValue((EffectParam)currentParam);
                }

                currentParam = (EffectParam)ParamterChooseBox.SelectedItem;
                index = ParamterChooseBox.SelectedIndex;

                // prepare fro new param
                EffectParam param = (EffectParam)currentParam;
                switch (param.ValueType)
                {
                    case ParameterType.Integer:
                        SwitchToInteger(index);
                        break;
                    case ParameterType.Real:
                        SwitchToReal(index);
                        break;
                    case ParameterType.List:
                        SwitchToList(index);
                        break;
                }
            }
            catch (IndexOutOfRangeException)
            {
                MessageBox.Show("Value is out of allowed range. It must be greater than lower bound and less then upper bound");
                ParamterChooseBox.SelectedItem = currentParam;
            }
            catch (Exception)
            {
                MessageBox.Show("Value or time is incorrect");
                ParamterChooseBox.SelectedItem = currentParam;
            }
        }

        #region time_text_boxes

        /// <summary>
        ///  Enables or disables Time text boxes: <see cref="AddParamChangeForm.MinTextBox"/>, <see cref="AddParamChangeForm.SecsTextBox"/>, <see cref="AddParamChangeForm.MilisTextBox"/> 
        /// </summary>
        /// <param name="enabled"> Enable/Disable argument </param>
        private void EnableTimeTextBoxe(bool enabled)
        {
            this.MinTextBox.Enabled = enabled;
            this.SecsTextBox.Enabled = enabled;
            this.MilisTextBox.Enabled = enabled;
        }

        /// <summary>
        ///  Sets given time to appropriate text boxes.
        /// </summary>
        /// <param name="time"> Time to show </param>
        private void SetTime(DateTime time)
        {
            this.MinTextBox.Text = time.Minute.ToString();
            this.SecsTextBox.Text = time.Second.ToString();
            this.MilisTextBox.Text = time.Millisecond.ToString();
        }


        /// <summary>
        ///  Gets time from textboxes.
        /// </summary>
        /// <returns> DateTime that represents values in Time text boxes: <see cref="AddParamChangeForm.MinTextBox"/>, <see cref="AddParamChangeForm.SecsTextBox"/>, <see cref="AddParamChangeForm.MilisTextBox"/> </returns>
        private DateTime GetTime()
        {
            int min = Convert.ToInt32(MinTextBox.Text);
            if (min < 0) throw new IndexOutOfRangeException();

            int secs = Convert.ToInt32(SecsTextBox.Text);
            if (secs < 0 || secs > 59) throw new IndexOutOfRangeException();

            int milis = Convert.ToInt32(MilisTextBox.Text);
            if (milis < 0 || milis > 999) throw new IndexOutOfRangeException();

            return new DateTime(1955, 9, 30, 0, min, secs, milis);
        }

        #endregion


        #region switch
        /// <summary>
        ///  Switches all components of <see cref="AddParamChangeForm"/> that they are working with int-type parameter. 
        /// </summary>
        /// <param name="index"> Index to the <see cref="AddParamChangeForm.parameters"/> of the param </param>
        private void SwitchToInteger(int index)
        {
            EffectParam param = parameters[index];
            
            // show and hide what necessary
            this.ListTypeComboBox.Hide();
            LowerBoundLabel.Show();
            UpperBoundLabel.Show();
            ValueBox.Show();

            // set bounds labels
            int[] bounds = (int[])param.Bounds;
            LowerBoundLabel.Text = bounds[0].ToString();
            UpperBoundLabel.Text = bounds[1].ToString();

            ValueBox.Text = "";
            // set value
            if (allParams && values[index] != null)
                ValueBox.Text = values[index].ToString();

        }

        /// <summary>
        ///  Switches all components of <see cref="AddParamChangeForm"/> that they are working with double-type parameter. 
        /// </summary>
        /// <param name="index"> Index to the <see cref="AddParamChangeForm.parameters"/> of the param </param>
        private void SwitchToReal(int index)
        {
            EffectParam param = parameters[index];
            
            // show and hide what necessary
            this.ListTypeComboBox.Hide();
            LowerBoundLabel.Show();
            UpperBoundLabel.Show();
            ValueBox.Show();
            
            // set bounds labels
            double[] bounds = (double[])param.Bounds;
            LowerBoundLabel.Text = bounds[0].ToString();
            UpperBoundLabel.Text = bounds[1].ToString();

            ValueBox.Text = "";
            // set value
            if (allParams && values[index] != null)
                ValueBox.Text = values[index].ToString();
        }

        /// <summary>
        ///  Switches all components of <see cref="AddParamChangeForm"/> that they are working with list-type parameter. 
        /// </summary>
        /// <param name="index"> Index to the <see cref="AddParamChangeForm.parameters"/> of the param </param>
        private void SwitchToList(int index)
        {
            EffectParam param = parameters[index];

            this.LowerBoundLabel.Hide();
            this.UpperBoundLabel.Hide();
            this.ValueBox.Hide();

            this.ListTypeComboBox.Show();
            this.ListTypeComboBox.Items.Clear();
            ListItem[] pairs = (ListItem[]) param.Bounds;

            int selected=-1;
            for (int i = 0; i < pairs.Length; i++)
            {
                this.ListTypeComboBox.Items.Add(pairs[i].DisplayName);
                if(values[index] != null && pairs[i].Value.Equals(values[index]))
                    selected = i;
            }

            // select the value
            if(allParams && values[index] != null)
                this.ListTypeComboBox.SelectedIndex = selected;
        }

        #endregion

        /// <summary>
        ///  <see cref="AddParamChangeForm.OkButton"/> click event handler
        /// </summary>
        /// <param name="sender"> Object that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        /// <remarks>
        ///  <para>
        ///   This method test values and closes the form if everything is all right.
        ///  </para>
        /// </remarks>
        private void OkButton_Click(object sender, EventArgs e)
        {
            try
            {
                // param working with
                EffectParam param;
                
                
                try
                {
                    // try to get selected parameter
                    param = (EffectParam)this.ParamterChooseBox.SelectedItem;
                }
                catch (NullReferenceException ex)
                {
                    // the value hasn't been changed yet 
                    DialogResult reply = MessageBox.Show("Some values haven't been set. Do you want to set them now? (No will cancel all set value)", "Not all values have been set", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                    if (reply == DialogResult.Yes)
                        return;
                    else
                    {
                        this.ok = false;
                        this.Close();
                        return;
                    }
                }
                
                // we successfully have parameter
                int index = this.ParamterChooseBox.SelectedIndex;

                // save currentKey and currentValue
                currentKey = param.Key;
                currentValue = GetCurrentValue(param);
                values[index] = currentValue;

                if (!allParams)
                    // save time
                    //currentTime = Convert.ToInt32(this.MinTextBox.Text);
                    currentTime = GetTime();
                if (allParams && !okayAllParams())
                {
                    // All parameter check hasn't been successful
                    DialogResult reply = MessageBox.Show("Some values haven't been set. Do you want to set them now? (No will cancel all set value)", "Not all values have been set", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                    if (reply == DialogResult.Yes)
                        return;
                    else
                    {
                        this.ok = false;
                        this.Close();
                        return;
                    }
                }

                // everthing fine, set ok and close
                this.ok = true;
                this.Close();
            }
            catch (IndexOutOfRangeException)
            {
                MessageBox.Show("Value is out of allowed range. It must be greater than lower bound and less then upper bound");
            }
            catch (Exception)
            {
                MessageBox.Show("Value or time is incorrect");
            }
        }

        /// <summary>
        ///  Returns value that is set by user.
        /// </summary>
        /// <param name="param"> <see cref="global::Effects.Effects.EffectParam"/> to determine value type </param>
        /// <returns> Value set by user, in a manner defined by <see cref="global::Effects.Effects.EffectParam"/> </returns>
        private object GetCurrentValue(EffectParam param)
        {
            //currentKey = param.Key;
            object value = null;
            switch (param.ValueType)
            {
                case ParameterType.Integer:
                    if (ValueBox.Text == "")
                        return null;
                    value = Convert.ToInt32(ValueBox.Text);
                    if (!okayInt((int)value, param.Bounds))
                        throw new IndexOutOfRangeException();
                    break;
                case ParameterType.Real:
                    if (ValueBox.Text == "")
                        return null;
                    value = Convert.ToDouble(ValueBox.Text);
                    if (!okayDouble((double)value, param.Bounds))
                        throw new IndexOutOfRangeException();
                    break;
                case ParameterType.List:
                    if (ListTypeComboBox.SelectedItem == null)
                        return null;
                    ListItem[] pairs = (ListItem[])param.Bounds;
                    for (int i = 0; i < pairs.Length; i++)
                        if (pairs[i].DisplayName == (string)ListTypeComboBox.SelectedItem)
                            value = pairs[i].Value;
                    break;
            }
            return value;
        }

        /// <summary>
        ///  Signalizes wheter <see cref="AddParamChangeForm"/> has been closed correctly, so values in <see cref="AddParamChangeForm.Key"/>, 
        ///  <see cref="AddParamChangeForm.Time"/>, <see cref="AddParamChangeForm.Value"/> and <see cref="AddParamChangeForm.Values"/> can be used.
        /// </summary>
        private bool ok;

        /// <summary>
        ///  Gets information whether <see cref="AddParamChangeForm"/> has been closed correctly, so values in <see cref="AddParamChangeForm.Key"/>, 
        ///  <see cref="AddParamChangeForm.Time"/>, <see cref="AddParamChangeForm.Value"/> and <see cref="AddParamChangeForm.Values"/> can be used.
        /// </summary>
        public bool OK
        {
            get { return ok; }
        }

        /// <summary>
        ///  <see cref="AddParamChangeForm.CancelButton"/> click event handler
        /// </summary>
        /// <param name="sender"> Object that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        private void CancelButton_Click(object sender, EventArgs e)
        {
            this.ok = false;
            Close();
        }

        // testing bounds for integer and real values
        #region testing_bounds

        /// <summary>
        ///  Test given int to be bounded by bounds
        /// </summary>
        /// <param name="value"> Value to test </param>
        /// <param name="bounds"> Bounds in the way, they are specified in <see cref="global::Effects.Effects.EffectParam"/> </param>
        /// <returns> true if value is bounded, false othervise </returns>
        private bool okayInt(int value, object bounds)
        {
            int lowerBound = ((int[])bounds)[0];
            int upperBound = ((int[])bounds)[1];

            return (lowerBound <= value && value <= upperBound);
        }

        /// <summary>
        ///  Test given double to be bounded by bounds
        /// </summary>
        /// <param name="value"> Value to test </param>
        /// <param name="bounds"> Bounds in the way, they are specified in <see cref="global::Effects.Effects.EffectParam"/> </param>
        /// <returns> true if value is bounded, false othervise </returns>
        private bool okayDouble(double value, object bounds)
        {
            double lowerBound = ((double[])bounds)[0];
            double upperBound = ((double[])bounds)[1];

            return (lowerBound <= value && value <= upperBound);
        }

        /// <summary>
        ///  Test all values (all items in <see cref="AddParamChangeForm.values"/> array) are not null
        /// </summary>
        /// <returns> true if all items in <see cref="AddParamChangeForm.values"/> are not null, false othervise </returns>
        private bool okayAllParams()
        {
            bool ret = true;
            for (int i = 0; i < parameters.Length; i++)
                if (values[i] == null)
                    ret = false;

            return ret;
        }

        #endregion

    }
}
